﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Mail;
using System.Text;

using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Events;
using mvcForum.Core.Interfaces.Events;
using mvcForum.Web.Extensions;

using SimpleLocalisation;
using mvcForum.Core.Interfaces.Data;
using mvcForum.Core.Interfaces.Services;

namespace mvcForum.Web.Events {

	/// <summary>
	/// Event listener class that will inform users following topics/forums about new posts/topics.
	/// </summary>
	public class FollowingEventListener :
											IEventListener<NewPostEvent>,
											IEventListener<NewTopicEvent>,
											IEventListener<PostFlagUpdatedEvent>,
											IEventListener<TopicFlagUpdatedEvent> {

		private readonly IRepository<Post> postRepo;
		private readonly IRepository<Topic> topicRepo;
		private readonly IConfiguration config;
		private readonly ILogger logger;
		private readonly IMailService mailService;
		// TODO: Exchange this with an interface!!!
		private readonly TextManager texts;

		public FollowingEventListener(IRepository<Post> postRepo, IRepository<Topic> topicRepo, IMailService mailService, IConfiguration config, ILogger logger, TextManager texts) {
			this.postRepo = postRepo;
			this.topicRepo = topicRepo;
			this.config = config;
			this.logger = logger;
			this.texts = texts;
			this.mailService = mailService;
		}

		#region Handle methods
		public void Handle(Object payload) {
			if (payload is NewTopicEvent) {
				this.Handle((NewTopicEvent)payload);
			}
			else if (payload is NewPostEvent) {
				this.Handle((NewPostEvent)payload);
			}
			else if (payload is TopicFlagUpdatedEvent) {
				this.Handle((TopicFlagUpdatedEvent)payload);
			}
			else if (payload is PostFlagUpdatedEvent) {
				this.Handle((PostFlagUpdatedEvent)payload);
			}
			else {
				throw new ApplicationException("Unknown payload!");
			}
		}

		public void Handle(NewTopicEvent payload) {
			Topic topic = this.topicRepo.Read(payload.TopicId);

			if ((topic.Flag & TopicFlag.Deleted) == 0 && (topic.Flag & TopicFlag.Quarantined) == 0) {

				this.SendToForumFollowers(topic, payload.ForumRelativeURL);
			}
		}

		public void Handle(NewPostEvent payload) {
			Post post = this.postRepo.Read(payload.PostId);

			if (post.Flag != PostFlag.Deleted && post.Flag != PostFlag.Quarantined) {

				SendToTopicFollowers(post, payload.TopicRelativeURL);
			}
		}

		public void Handle(TopicFlagUpdatedEvent payload) {
			Topic topic = this.topicRepo.Read(payload.TopicId);

			if (((payload.OriginalFlag & TopicFlag.Deleted) != 0 || (payload.OriginalFlag & TopicFlag.Quarantined) != 0)
				&& (topic.Flag & TopicFlag.Quarantined) == 0 && (topic.Flag & TopicFlag.Deleted) == 0) {

				this.SendToForumFollowers(topic, payload.ForumRelativeURL);
			}
		}

		public void Handle(PostFlagUpdatedEvent payload) {
			Post post = this.postRepo.Read(payload.PostId);

			if ((payload.OriginalFlag == PostFlag.Deleted || payload.OriginalFlag == PostFlag.Quarantined)
				&& post.Flag != PostFlag.Deleted && post.Flag != PostFlag.Quarantined) {

				SendToTopicFollowers(post, payload.TopicRelativeURL);
			}
		}
		#endregion

		private void SendToForumFollowers(Topic topic, String actionURL) {
			// Get the site's URL from the configuration.
			String siteURL = this.config.SiteURL;
			if (siteURL.EndsWith("/")) {
				siteURL = siteURL.Substring(0, siteURL.Length - 1);
			}
			// Merge site URL and view forum URL.
			String url = String.Format("{0}{1}", siteURL, actionURL);

			// Let's create a cache for the subject and body (the language will be the key)
			Dictionary<String, String> subjects = new Dictionary<String, String>();
			Dictionary<String, String> bodies = new Dictionary<String, String>();
			// Iterate all the followers!
			foreach (FollowForum ff in topic.Forum.Followers) {
				// Is this follower not the author and does the follower have an e-mail address?
				if (ff.ForumUser.Id != topic.Author.Id && !String.IsNullOrWhiteSpace(ff.ForumUser.EmailAddress)) {
					// Yes, get the culture/language from the user's settings.
					String culture = ff.ForumUser.Culture;
					// Do we have a cached version of the subject and body for this language?
					if (!subjects.ContainsKey(culture)) {
						// Nope, make one then!

						// Create a LanguageInfo instance that matches the user's settings.
						// TODO:
						//LanguageInfo userLI = new LanguageInfo { Key = culture };

						// Cache subject!
						subjects.Add(culture, this.texts.Get<mvcForum.Web.ForumConfigurator>("FollowForum.EmailSubject", new { Topic = topic, Forum = topic.Forum }/*, userLI*/));
						// Cache body!
						bodies.Add(culture, this.texts.Get<mvcForum.Web.ForumConfigurator>("FollowForum.EmailBody", new { Topic = topic, Forum = topic.Forum, Link = url }/*, userLI*/));
					}

					// Send the e-mail!
					this.SendMail(ff.ForumUser.EmailAddress, ff.ForumUser.Name, culture, subjects[culture], bodies[culture]);
				}
			}
		}

		private void SendToTopicFollowers(Post post, String actionURL) {
			// Get the site's URL from the configuration.
			String siteURL = this.config.SiteURL;
			if (siteURL.EndsWith("/")) {
				siteURL = siteURL.Substring(0, siteURL.Length - 1);
			}
			// Merge site URL and view topic URL.
			String url = String.Format("{0}{1}", siteURL, actionURL);

			// Let's create a cache for the subject and body (the language will be the key)
			Dictionary<String, String> subjects = new Dictionary<String, String>();
			Dictionary<String, String> bodies = new Dictionary<String, String>();
			// Iterate all the followers!
			foreach (FollowTopic ft in post.Topic.Followers) {
				// Is this follower not the author and does the follower have an e-mail address?
				if (ft.ForumUser.Id != post.Author.Id && !String.IsNullOrWhiteSpace(ft.ForumUser.EmailAddress)) {
					// Yes, get the culture/language from the user's settings.
					String culture = ft.ForumUser.Culture;
					// Do we have a cached version of the subject and body for this language?
					if (!subjects.ContainsKey(culture)) {
						// Nope, make one then!

						// Create a LanguageInfo instance that matches the user's settings.
						//LanguageInfo userLI = new LanguageInfo { Key = culture };

						// Cache subject!
						subjects.Add(culture, this.texts.Get<mvcForum.Web.ForumConfigurator>("FollowTopic.EmailSubject", new { Post = post, Topic = post.Topic }/*, userLI*/));
						// Cache body!
						bodies.Add(culture, this.texts.Get<mvcForum.Web.ForumConfigurator>("FollowTopic.EmailBody", new { Post = post, Topic = post.Topic, Link = url }/*, userLI*/));
					}

					// Send the e-mail!
					this.SendMail(ft.ForumUser.EmailAddress, ft.ForumUser.Name, culture, subjects[culture], bodies[culture]);
				}
			}
		}

		private void SendMail(String recipientEmail, String recipientName, String culture, String subject, String body) {
			//// TODO: Put this in a queue?? Do a daily digest! Optional?
			////this.mailService.Send(this.config.RobotEmailAddress, recipient, subject, body);
			//MailMessage message = new MailMessage();
			//// Get the "from address" and "from name" from the configuration.
			//message.From = new MailAddress(this.config.RobotEmailAddress, this.config.RobotName);
			//// And let's put the user's e-mail address in the to field.
			//message.To.Add(new MailAddress(recipientEmail, recipientName));
			//// UTF8 FTW!
			//message.SubjectEncoding = Encoding.UTF8;
			//message.BodyEncoding = Encoding.UTF8;

			//message.Subject = subject;
			//message.Body = body;
			//message.IsBodyHtml = false;
			try {
				// Create a SMTP client, this will use the settings from the web.config file.
				//SmtpClient client = new SmtpClient();
				//// Send the message.
				//client.Send(message);

				this.mailService.Send(
					new MailAddress(this.config.RobotEmailAddress, this.config.RobotName),
					new MailAddress[] { new MailAddress(recipientEmail, recipientName) }.ToList(),
					subject,
					body);

				logger.Log(EventType.Debug, String.Format("The e-mail to user {0} ({1}) has been sent.", recipientName, recipientEmail));
			}
			catch (Exception ex) {
				logger.Log(EventType.Error, String.Format("Could not send e-mail to {0}, MessageAdded", recipientEmail), ex);
			}
		}

		public Boolean RunAsynchronously {
			get {
				return false;
			}
		}

		public Byte Priority {
			get {
				return (Byte)EventPriority.Lowest;
			}
		}

		public Boolean UniqueEvent {
			get {
				return false;
			}
		}
	}
}